package com.finalx.GrayCamera;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.provider.MediaStore;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Bitmap.Config;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;

public class GrayCameraActivity extends Activity implements OnClickListener,
		SurfaceHolder.Callback {

	private final String TAG = "GrayCameraActivity";

	private final String CAMERA_DIR = Environment.getExternalStorageDirectory()
			.toString() + "/DCIM/Camera";

	private Camera mCamera = null;
	private int mCameraId = CameraInfo.CAMERA_FACING_FRONT;
	private Camera.Parameters mParameters = null;

	private int mPreviewSizeW = 720;
	private int mPreviewSizeH = 480;
	private int mSurfaceViewSizeW = 1080;
	private int mSurfaceViewSizeH = 720;

	private int mPictureSizeW = 640;
	private int mPictureSizeH = 480;

	private LayoutParams mLayoutParam;

	private SurfaceHolder mHolder = null;
	private SurfaceView mSurfaceView = null;
	// private ImageView mThumbnailView = null;
	private Button mButtonCapture = null;
	private TextView mTextViewRecognized = null;
	// private TextView mTextViewAllNames = null;
	private TextView mTextViewNeed3Face = null;
	private AlertDialog mInputFaceNameDlg = null;
	private View mInputFaceNameDlgView = null;
	private EditText mNameEditText = null;
	private ProgressBar mProgressBarTraining = null;

	private PreviewCallback mPreviewCallback = null;
	private byte[] mOrigFrame = null;
	private String mCaptureName = null;
	private String mWorkingDirectory = null;
	private String mNameUnknown = null;
	private int mTotalFaces = 0;

	private HashMap<String, Integer> mHashMapAllNames = null;
	private String mStringAllNames = null;

	private ContentResolver mContentResolver = null;

	private boolean mPreviewing = false;

	private boolean mTraining = false;
	private boolean mLastFrameProcessed = true;
	private String mLastDetectedFace = null;
	private boolean mLastTimeTrainingToo = false;

	private Runnable mRunnableTrain = new Runnable() {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			doTrainAndGetAllNames();
		}
	};

	private Runnable mRunnableProcessFrame = new Runnable() {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			doProcessFrame(mOrigFrame);

		}
	};

	private HandlerThread mHandlerThreadTrain = null;
	private HandlerThread mHandlerThreadProcessFrame = null;
	private Handler mHandlerTrain = null;
	private Handler mHandlerProcessFrame = null;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		Log.d(TAG, "@@@@_ onCreate()");
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		mButtonCapture = (Button) findViewById(R.id.button_capture);
		mSurfaceView = (SurfaceView) findViewById(R.id.preview_surface);
		mTextViewRecognized = (TextView) findViewById(R.id.recognized_name);
		// mTextViewAllNames = (TextView) findViewById(R.id.all_names);
		mTextViewNeed3Face = (TextView) findViewById(R.id.need_3_face);
		mProgressBarTraining = (ProgressBar) findViewById(R.id.progressbar_training);
		// mThumbnailView = (ImageView) findViewById(R.id.thumbnail_view);
		mSurfaceView.measure(
				MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
				MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

		mSurfaceView.setOnClickListener(this);
		mButtonCapture.setOnClickListener(this);

		mPreviewCallback = new PreviewCallback() {
			@Override
			public void onPreviewFrame(byte[] data, Camera camera) {
				// TODO Auto-generated method stub
				if (mTraining) {
					// will not detect or recognize
					if(!mLastTimeTrainingToo) {
						mTextViewRecognized.setText("Waiting ...");
						mTextViewRecognized.setTextColor(android.graphics.Color.RED);
						mTextViewRecognized.setVisibility(View.VISIBLE);
						mButtonCapture.setVisibility(View.INVISIBLE);
						mTextViewNeed3Face.setVisibility(View.INVISIBLE);
						mLastTimeTrainingToo = true;
					}
					return;
				} 
				
				mLastTimeTrainingToo = false;
				mOrigFrame = data;
				if (mLastFrameProcessed) {
					Log.d(TAG, "onPreviewFrame: post runnable to process frame");
					mHandlerProcessFrame.post(mRunnableProcessFrame);

					// mTextViewAllNames.setText("Total Faces Trained: " +
					// mTotalFaces);
					// mTextViewAllNames.setTextColor(android.graphics.Color.YELLOW);

					String text2hint = "";
					
					if(mTotalFaces < 3) {
						mTextViewNeed3Face.setVisibility(View.VISIBLE);
						if(mLastDetectedFace == null){
							text2hint = getString(R.string.need_3_faces) 
								+"\nCurrent has " + mTotalFaces +  "Faces";
							mButtonCapture.setVisibility(View.INVISIBLE);
							mTextViewRecognized.setText("Couldn't found face");
							mTextViewRecognized.setTextColor(android.graphics.Color.WHITE);
							mTextViewRecognized.setVisibility(View.VISIBLE);
						} else{
							text2hint = "<<- Press 'Register' Button to Add this face\n"
								+ getString(R.string.need_3_faces) 
								+"\nCurrent has " + mTotalFaces +  " Faces";
							
							mButtonCapture.setVisibility(View.VISIBLE);
							mTextViewRecognized.setVisibility(View.INVISIBLE);
						}
						
					} else {
						if(mLastDetectedFace == null){
							mTextViewRecognized.setText("Couldn't found face");
							mTextViewRecognized.setTextColor(android.graphics.Color.WHITE);
							mButtonCapture.setVisibility(View.INVISIBLE);
							mTextViewNeed3Face.setVisibility(View.INVISIBLE);
						} else{
							if(mLastDetectedFace.equalsIgnoreCase("unknown")){
								mTextViewRecognized.setTextColor(android.graphics.Color.WHITE);
							} else {
								mTextViewRecognized.setTextColor(android.graphics.Color.GREEN);
							}
							mTextViewRecognized.setText(mLastDetectedFace);
							mButtonCapture.setVisibility(View.VISIBLE);
							text2hint = "<<- Press 'Register' Button to Add this face";
							mTextViewNeed3Face.setVisibility(View.VISIBLE);
						}
						mTextViewRecognized.setVisibility(View.VISIBLE);
					}
					mTextViewNeed3Face.setText(text2hint);
				}
			}
		};

		LayoutInflater factory = LayoutInflater.from(this);
		mInputFaceNameDlgView = factory.inflate(R.layout.dialog, null);
		mInputFaceNameDlg = new AlertDialog.Builder(this)
				.setTitle("Enter Name")
				.setView(mInputFaceNameDlgView)
				.setCancelable(false)
				.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						// TODO Auto-generated method stub
						mNameEditText = (EditText) mInputFaceNameDlgView
								.findViewById(R.id.name_editor);
						mCaptureName = mNameEditText.getText().toString()
								.replaceAll(" ", "");
						if (mCaptureName != null && mCaptureName.length() != 0) {
							captureThisFrame(mFrameInt, mPreviewSizeW,
									mPreviewSizeH);
							mHandlerTrain.post(mRunnableTrain);
						}

						startPreview();
					}
				})
				.setNegativeButton("Cancel",
						new DialogInterface.OnClickListener() {

							@Override
							public void onClick(DialogInterface dialog,
									int which) {
								// TODO Auto-generated method stub
								startPreview();
							}
						}).create();

		// mThumbnailView.setOnClickListener(this);
		mHolder = mSurfaceView.getHolder();
		mHolder.addCallback(this);
		mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

		mContentResolver = getContentResolver();

		mHandlerThreadProcessFrame = new HandlerThread("process_frame");
		mHandlerThreadProcessFrame.start();
		mHandlerProcessFrame = new Handler(
				mHandlerThreadProcessFrame.getLooper());

		mHandlerThreadTrain = new HandlerThread("train");
		mHandlerThreadTrain.start();
		mHandlerTrain = new Handler(mHandlerThreadTrain.getLooper());

		initNativeEnviorment();
		mHashMapAllNames = getAllNames();
		mNameUnknown = GrayCameraJni.getNameUnknown();

	}

	private void initNativeEnviorment() {
		// TODO Auto-generated method stub
		mWorkingDirectory = GrayCameraJni.getWorkingDirectory();
		Log.d(TAG, "working dir:" + mWorkingDirectory);
		CopyAssets("consists", mWorkingDirectory + "/");
		CopyAssets("orl_faces_base", mWorkingDirectory + "/faces/");
		GrayCameraJni.init();
	}

	private void CopyAssets(String assetDir, String destDir) {
		String[] files;
		try {
			files = this.getResources().getAssets().list(assetDir);
		} catch (IOException e1) {
			return;
		}
		File mWorkingPath = new File(destDir);
		// if this directory does not exists, make one.
		if (!mWorkingPath.exists()) {
			if (!mWorkingPath.mkdirs()) {
				Log.e("--CopyAssets--", "cannot create directory.");
			}
		}

		for (int i = 0; i < files.length; i++) {
			try {
				String fileName = files[i];
				// we make sure file name not contains '.' to be a folder.
				if (!fileName.contains(".")) {
					if (0 == assetDir.length()) {
						CopyAssets(fileName, destDir + fileName + "/");
					} else {
						CopyAssets(assetDir + "/" + fileName, destDir
								+ fileName + "/");
					}
					continue;
				}
				File outFile = new File(mWorkingPath, fileName);
				if (outFile.exists())
					outFile.delete();
				InputStream in = null;
				if (0 != assetDir.length())
					in = getAssets().open(assetDir + "/" + fileName);
				else
					in = getAssets().open(fileName);
				OutputStream out = new FileOutputStream(outFile);

				// Transfer bytes from in to out
				byte[] buf = new byte[1024];
				int len;
				while ((len = in.read(buf)) > 0) {
					out.write(buf, 0, len);
				}

				in.close();
				out.close();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	protected void setSurfaceSize() {
		//
		// int width = mFrist ? 800 : mSurfaceView.getMeasuredWidth();
		// int height = mFrist ? 600 : mSurfaceView.getMeasuredHeight();
		// Log.d(TAG, "surface layout ratio w/h: " + width + "/" + height);
		//
		// float ratio = width / height;
		// if(!mFrist && ratio == mSurfaceViewSizeW/mSurfaceViewSizeH) {
		// mFrist = false;
		// return;
		// }
		// mFrist = false;
		//
		// if(ratio > mPreviewSizeW / mPreviewSizeH) {
		// mSurfaceViewSizeH = height;
		// mSurfaceViewSizeW = mSurfaceViewSizeH *
		// (mPreviewSizeW/mPreviewSizeH);
		// } else {
		// mSurfaceViewSizeW = width;
		// mSurfaceViewSizeH = mSurfaceViewSizeW /
		// (mPreviewSizeW/mPreviewSizeH);
		// }
		//
		mLayoutParam = mSurfaceView.getLayoutParams();
		mLayoutParam.width = mSurfaceViewSizeW;
		mLayoutParam.height = mSurfaceViewSizeH;

		mSurfaceView.setLayoutParams(mLayoutParam);
	}

	@Override
	protected void onResume() {
		// TODO Auto-generated method stub
		super.onResume();
		Log.d(TAG, "@@ onresume9()");

		// mSurfaceView.setVisibility(View.VISIBLE);
		startPreview();

		String absPath = "file:///android_asset/";
		Log.d(TAG, "assestsFiles:" + absPath);
	}

	protected void onPause() {
		super.onStop();
		Log.d(TAG, "@@ onPause()");

		stopPreview();
		mCamera.release();
		mCamera = null;
	}

	public void startPreview() {

		if (mPreviewing) {
			stopPreview();
		}
		Log.d(TAG, "@@startPreview~~");
		initCamera();

		mCamera.setPreviewCallback(mPreviewCallback);

		mCamera.startPreview();

		Log.d(TAG, "starting preview");
		mPreviewing = true;

	}

	public void stopPreview() {
		if (mPreviewing) {
			mCamera.setPreviewCallback(null);
			mCamera.stopPreview();
			mPreviewing = false;
			Log.d(TAG, "stop Preview");
		}
	}

	public void initCamera() {
		if (mCamera == null) {
			mCamera = Camera.open(mCameraId);
		}
		mParameters = mCamera.getParameters();

		List<Size> mSupportedPreviewSizes = mParameters
				.getSupportedPreviewSizes();

		Size thisSize;
		String sizeString = new String();

		if (mSupportedPreviewSizes != null) {
			for (int i = 0; i < mSupportedPreviewSizes.size(); i++) {
				thisSize = mSupportedPreviewSizes.get(i);
				sizeString = sizeString + thisSize.width + "x"
						+ thisSize.height + ", ";
			}
			Log.d(TAG, "@@supported preview size:" + sizeString);
		}

		mParameters.setPictureSize(mPictureSizeW, mPictureSizeH);
		mParameters.setPreviewSize(mPreviewSizeW, mPreviewSizeH);
		mCamera.setParameters(mParameters);
		try {
			mCamera.setPreviewDisplay(mHolder);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	long startMillis = 0;

	int[] mFrameInt = new int[mPreviewSizeW * mPreviewSizeH];

	private void doProcessFrame(byte[] data) {

		mLastFrameProcessed = false;
		startMillis = System.currentTimeMillis();
		decodeYUV(mFrameInt, data, mPreviewSizeW, mPreviewSizeH);
		Log.d(TAG, "decodeYUV() using "
				+ (System.currentTimeMillis() - startMillis) + " millis");

		mLastDetectedFace = GrayCameraJni.hasHead(mFrameInt, mPreviewSizeW,
				mPreviewSizeH, true);
		mLastFrameProcessed = true;
	}

	private void captureThisFrame(int[] pix, int w, int h) {
		int[] resultInt = GrayCameraJni.saveHead(pix, w, h, mCaptureName);
		Log.d(TAG, "resultIng.len = " + resultInt.length);

		if (resultInt.length == 1) {
			// no face found
			return;
		}
		Bitmap resultImg = Bitmap.createBitmap(w, h, Config.RGB_565);
		resultImg.setPixels(resultInt, 0, w, 0, 0, w, h);
	}

	public static void decodeYUV(int[] out, byte[] fg, int width, int height)
			throws NullPointerException, IllegalArgumentException {
		final int sz = width * height;
		if (out == null)
			throw new NullPointerException("buffer 'out' is null");
		if (out.length < sz)
			throw new IllegalArgumentException("buffer 'out' size "
					+ out.length + " < minimum " + sz);
		if (fg == null)
			throw new NullPointerException("buffer 'fg' is null");
		if (fg.length < sz)
			throw new IllegalArgumentException("buffer 'fg' size " + fg.length
					+ " < minimum " + sz * 3 / 2);
		int i, j;
		int Y, Cr = 0, Cb = 0;
		for (j = 0; j < height; j++) {
			int pixPtr = j * width;
			final int jDiv2 = j >> 1;
			for (i = 0; i < width; i++) {
				Y = fg[pixPtr];
				if (Y < 0)
					Y += 255;
				if ((i & 0x1) != 1) {
					final int cOff = sz + jDiv2 * width + (i >> 1) * 2;
					Cb = fg[cOff];
					if (Cb < 0)
						Cb += 127;
					else
						Cb -= 128;
					Cr = fg[cOff + 1];
					if (Cr < 0)
						Cr += 127;
					else
						Cr -= 128;
				}
				int R = Y + Cr + (Cr >> 2) + (Cr >> 3) + (Cr >> 5);
				if (R < 0)
					R = 0;
				else if (R > 255)
					R = 255;
				int G = Y - (Cb >> 2) + (Cb >> 4) + (Cb >> 5) - (Cr >> 1)
						+ (Cr >> 3) + (Cr >> 4) + (Cr >> 5);
				if (G < 0)
					G = 0;
				else if (G > 255)
					G = 255;
				int B = Y + Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6);
				if (B < 0)
					B = 0;
				else if (B > 255)
					B = 255;
				out[pixPtr++] = 0xff000000 + (B << 16) + (G << 8) + R;
			}
		}

	}

	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		// TODO Auto-generated method stub
		Log.d(TAG, "@@@@ surface changed");
		mHolder = holder;
		try {
			if (holder.isCreating()) {

				mCamera.setPreviewDisplay(mHolder);
			} else {
				if (!(mPreviewing)) {
					startPreview();
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public void surfaceCreated(SurfaceHolder holder) {
		// TODO Auto-generated method stub
		Log.d(TAG, "@@ surface created");
		setSurfaceSize();

	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		// TODO Auto-generated method stub
		Log.d(TAG, "@@ surface destroyed");
		mHolder = null;
	}

	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		switch (v.getId()) {
		case R.id.button_capture:
			if (!mTraining) {
				stopPreview();
				mInputFaceNameDlg.show();
				break;
			}
		}
	}

	private void switchCamera() {
		// TODO Auto-generated method stub
		stopPreview();
		mCamera.release();
		mCamera = null;

		mCameraId = (mCameraId == CameraInfo.CAMERA_FACING_FRONT) ? CameraInfo.CAMERA_FACING_BACK
				: CameraInfo.CAMERA_FACING_FRONT;
		startPreview();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.layout.option_menu, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle item selection
		switch (item.getItemId()) {
		case R.id.menu_item_train:
			mHandlerTrain.post(mRunnableTrain);
			break;

		case R.id.menu_item_reset:
			mCamera.setPreviewCallback(null);
			GrayCameraJni.reset();
			CopyAssets("orl_faces_base", mWorkingDirectory + "/faces/");
			mHashMapAllNames = getAllNames();
			Toast.makeText(getApplication(), "all Face trained is cleared",
					Toast.LENGTH_SHORT).show();
			mCamera.setPreviewCallback(mPreviewCallback);
			break;

		case R.id.menu_item_switch_camera:
			switchCamera();
			Toast.makeText(getApplication(), "Camera switched",
					Toast.LENGTH_SHORT).show();
		default:
			break;
		}
		return super.onOptionsItemSelected(item);
	}

	private HashMap<String, Integer> getAllNames() {
		String flattened = GrayCameraJni.getAllNames();
		StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
		HashMap<String, Integer> namesMap = new HashMap<String, Integer>();
		mTotalFaces = 0;
		while (tokenizer.hasMoreElements()) {
			String kv = tokenizer.nextToken();
			int pos = kv.indexOf('=');
			if (pos == -1) {
				continue;
			}
			String k = kv.substring(0, pos);
			Integer v = Integer.parseInt(kv.substring(pos + 1));
			namesMap.put(k, v);
			mTotalFaces += (Integer) v;
		}

		mHashMapAllNames = namesMap;
		mStringAllNames = flattened;
		Log.d(TAG, "mStringAllNames = " + mStringAllNames);
		return mHashMapAllNames;
	}

	private void doTrainAndGetAllNames() {
		mTraining = true;
		// mProgressBarTraining.setVisibility(View.VISIBLE);
		// mProgressBarTraining.setProgress(0);
		mHashMapAllNames = getAllNames();
		if(mTotalFaces >= 3){
			GrayCameraJni.train();
		}
		// mProgressBarTraining.setVisibility(View.INVISIBLE);
		mTraining = false;
	}
}